package cs236369.webserver;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.protocol.HttpContext;




public class TSPEngine implements ITypeHandler {

	private final String mGeneratedClassName = "__GeneratedClass";
	private final String mBeginOfJavaCodeSymbol = "<?";
	private final String mEndOfJavaCodeSymbol = "?>";
	
	private String mPath;
	private DefaultHttpServerConnection mConnToClient;
	private Map<String, String> mHttpParams;
	private Map<Integer, ISession> mSessionHolder;
	private int mNextSessionId;
	private Integer mUniqueNumber;
	
	public TSPEngine(Properties p) {
		mSessionHolder = new HashMap<Integer, ISession>();
		mNextSessionId = 0;
		mUniqueNumber = 0;
		
	}
	
	@Override
	public void init(String path, DefaultHttpServerConnection connToClient, Map<String, String> httpParams) {
		mPath = path;
		mConnToClient = connToClient;
		mHttpParams = httpParams;
	}
	
	/* (non-Javadoc)
	 * @see org.apache.http.protocol.HttpRequestHandler#handle(org.apache.http.HttpRequest, org.apache.http.HttpResponse, org.apache.http.protocol.HttpContext)
	 */
	@Override
	public void handle
	(
			HttpRequest request,
			HttpResponse response,
			HttpContext context
	) throws HttpException, IOException {
		
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		PrintStream out = new PrintStream(byteArrayOutputStream);
		
		ISession session = null;
		int sessionId;
		
		// Check if a session alreay exists.
		synchronized(mSessionHolder) {
			if (request.containsHeader("Cookie")) {
				sessionId = Integer.parseInt(request.getFirstHeader("Cookie").getValue());
								
				if(!mSessionHolder.containsKey(sessionId)) {
					WebServerLog.log(this, "Bad cookie: " + sessionId + ", creating new session");
					sessionId = mNextSessionId++;
					session = new Session();				
					mSessionHolder.put(sessionId, session);
				}
				
				session = mSessionHolder.get(sessionId);
							
			}
			else {
				WebServerLog.log(this, "No Cookie header, creating new session: " + mNextSessionId); 
				sessionId = mNextSessionId++;
				session = new Session();				
				mSessionHolder.put(sessionId, session);
			}
			WebServerLog.log(this, "session: " + sessionId); 
		}
		
		// Generete here the dynamic class.
		
		int unique;
		synchronized (mUniqueNumber) {
			unique = mUniqueNumber++;
		}
		
		String javaTspFileName = mGeneratedClassName + unique;
		File javaTspFile = new File(javaTspFileName + ".java");
		if (javaTspFile.exists()) {
			WebServerLog.log(this, "File: " + javaTspFileName + ".java" + "Already exists! deleting the old one");
			javaTspFile.delete();
			
		}
		
		if(!javaTspFile.createNewFile()) {
			System.err.println("ERROR: cannot create new file: " + javaTspFile.getAbsolutePath());
			sendErrorMessage(response, "Internal server error (could not create TSP file)", "Internal server error", 500);
			return;						
		}
		
		PrintStream ps = new PrintStream(javaTspFile);
		ps.println("import java.io.PrintStream;");
		ps.println("import java.util.Map;");
		ps.println("import cs236369.webserver.IDynamicPage;");
		ps.println("import cs236369.webserver.ISession;");
		
		ps.println("public class " + javaTspFileName + " implements IDynamicPage {");
		ps.println("public void invoke(PrintStream out,ISession session,Map<String, String> params) {");
				
		FileInputStream fstream = new FileInputStream(mPath);
		DataInputStream in = new DataInputStream(fstream);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
	    
		String currentLine;
		boolean insideJavaCode = false;
		//Read File Line By Line
		while ((currentLine = br.readLine()) != null) {
			String strLine = currentLine;
			while (!strLine.isEmpty()) {
				if (insideJavaCode) {
					int idx = strLine.indexOf(mEndOfJavaCodeSymbol);
					if (idx == -1) {
						ps.println(strLine);
						strLine = "";
					}
					else {
						insideJavaCode = false;
						String restOfJavaCode = strLine.substring(0, idx);
						ps.println(restOfJavaCode);
						strLine = strLine.substring(idx + mEndOfJavaCodeSymbol.length());
												
					}
				}
				else { // insideJavaCode
					int idx = strLine.indexOf(mBeginOfJavaCodeSymbol);
					if (idx == -1) {
						strLine = strLine.replaceAll("\\\\", "\\\\\\\\");
						strLine = strLine.replaceAll("\"", "\\\\\"");
						ps.println("out.println(\"" + strLine + "\");");
						strLine = "";
					}
					else {
						insideJavaCode = true;
						String restOfNonJavaCode = strLine.substring(0, idx);
						restOfNonJavaCode = restOfNonJavaCode.replaceAll("\\\\", "\\\\\\\\");
						restOfNonJavaCode = restOfNonJavaCode.replaceAll("\"", "\\\\\"");
						ps.println("out.println(\"" + restOfNonJavaCode + "\");");
						
						strLine = strLine.substring(idx + mBeginOfJavaCodeSymbol.length());
					}
					
					
					
					
				}
			}// while (!currentLine.isEmpty()))
		} // while ((strLine = br.readLine()) != null)
		
		ps.println("\n}\n}");
		ps.close();
		// Done Generating Java code
		
		IDynamicPage dynamicPage = null;
		try {
			dynamicPage = (IDynamicPage)compileAndLoad(javaTspFileName + ".java", javaTspFileName).newInstance();
		} catch (InstantiationException e) {
			System.err.println("ERROR: cannot instansiate class: " + javaTspFileName);
			sendErrorMessage(response,
					         "Internal server error (cannot instansiate class " + javaTspFileName + ")<br>" + e.getMessage(),
					         "Internal server error", 500);
			return;				
		} catch (IllegalAccessException e) {
			System.err.println("ERROR: illegal access: " + javaTspFileName + ".java");
			sendErrorMessage(response,
					         "Internal server error (illegal access " + javaTspFileName + ")<br>" + e.getMessage(),
					         "Internal server error", 500);
			return;				
		} catch (ClassNotFoundException e) {
			System.err.println("ERROR: class not found: " + javaTspFileName);
			sendErrorMessage(response,
					         "Internal server error (class not found " + javaTspFileName + ")<br>" + e.getMessage(),
					         "Internal server error", 500);
			return;				
		
		} catch (Exception e) {
			System.err.println("ERROR: General exception: " + e.getMessage());
			sendErrorMessage(response,
				         	"Internal server error<br>" + e.getMessage(),
				         	"Internal server error", 500);
			return;		
		}
		
				
		dynamicPage.invoke(out, session, mHttpParams);
		out.close();
		
		
		 ByteArrayEntity entity = new ByteArrayEntity(byteArrayOutputStream.toByteArray());
		 response.setEntity(entity);
		 response.addHeader("Set-Cookie", Integer.toString(sessionId));
					 
		 sendResponseToClient(response);
		 
		 File classTspFile = new File("bin" + File.separator + javaTspFileName + ".class");
		 if (!classTspFile.delete()) {
			 System.err.println("ERROR: Cannot delete " + classTspFile.getAbsolutePath());
			 classTspFile.deleteOnExit();
			 
		 }
		 
		 if (!javaTspFile.delete()) {
			 System.err.println("ERROR: Cannot delete " + javaTspFile.getAbsolutePath());
			 javaTspFile.deleteOnExit();
		 }
		 	 
	}
	
	private Class<?> compileAndLoad(String srcPath,String qualifiedClassName) throws Exception {
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		if (compiler == null)
			throw new Exception("compiler not found");
		StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
		if (manager == null)
			throw new Exception("compiler returned null file namager");
		
		Iterable<? extends JavaFileObject> units = manager
				.getJavaFileObjects(srcPath);
		Boolean status = compiler.getTask(null, manager, null,
				Arrays.asList(new String[] { "-d", "bin" }), null, units)
				.call();
		if (status == null || !status.booleanValue()) {
			WebServerLog.log(this, "Compilation failed");
			throw new Exception("compilation fail");
		} else {
			WebServerLog.log(this, "Compilation successful!!!\n");
		}

		return manager.getClassLoader(
				javax.tools.StandardLocation.CLASS_PATH).loadClass(
						qualifiedClassName);
	}
	
	private void sendErrorMessage(HttpResponse response, String message, String reasonPhrase, int errorCode){
		String page = 
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
			"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" +
			"<head>" +
			"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />" +
			"<title>" + "Http Error " + errorCode + "</title> " +
			"</head>" +
			"<body>" +
			message +
			"</body>" +
			"</html>";
			
		response.setStatusCode(errorCode);
		response.setReasonPhrase(reasonPhrase);
		
		try {
			StringEntity entity = new StringEntity(page);
			response.setEntity(entity);
		} catch (UnsupportedEncodingException e1) {
			System.err.println("ERROR: Unsupported encoding: " + e1.getMessage());
		}
		
		sendResponseToClient(response);
	}
	
	private void sendResponseToClient(HttpResponse response) {
		 SimpleDateFormat Date=new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",new Locale("en","US"));
		 Date.setTimeZone(TimeZone.getTimeZone("GMT"));
		 response.addHeader("Date", Date.format(new java.util.Date()));
		 response.addHeader("Connection","close");
		 response.addHeader("Content-Type","text/html");
		 String contentLength = Long.toString(response.getEntity().getContentLength());
		 response.addHeader("Content-length",contentLength);
		 response.addHeader("Cache-Control","no-store");
		 		
		 try {
			mConnToClient.sendResponseHeader(response);
			mConnToClient.sendResponseEntity(response);
		} catch (HttpException e) {
			System.err.println("ERROR: http error: " + e.getMessage());
			return;
		} catch (IOException e) {
			System.err.println("ERROR: IO error: " + e.getMessage());
			return;
		}
		
	}
	

}

